﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using QstkCore.Core;
using QstkCore.DataAccess;
using QstkCore.Domain;
using QstkCore.Util;

namespace Examples
{
    static class EventProfilerExample
    {
        // Available field names: Open, High, Low, Close, Volume, Adj Close,
        // Note in python QSTK, close -> means Adj Close and actual_close -> Close        
        // Since we only use Yahoo data there is no mapping...
        private const string closefield = "Adj Close"; 
        private const int window = 20;

        static void Main()
        {
            List<string> symbols;

            const string stocksfile = "SP500port.csv";
            using (var csv = new CsvReader2(stocksfile)) 
            {
                symbols = csv.AsEnumerable().Select(r => r[0]).OrderBy(r => r).ToList();
            }

            var startday = new DateTime(2008, 1, 1);
            var endday = new DateTime(2010, 12, 31);

//            symbols = new List<string>(){"PLD", "SPY"};
//            var startday = new DateTime(2008, 9, 30);
//            var endday = new DateTime(2008, 10, 9);

            var eventMatrix = FindEvents(symbols, startday, endday, marketSymbol: "SPY");

            var eventProfiler = new EventProfiler(eventMatrix, startday, endday, lookbackDays: window, lookforwardDays: window);

            eventProfiler.Study(filename: "MyEventStudy.pdf", plotErrorBars: true, plotMarketNeutral: true,
                plotEvents: false, marketSymbol: "SPY");
        }

        private static ConcurrentDictionary<string, IList<int>> FindEvents(IEnumerable<string> symbols1, DateTime startday, DateTime endday, string marketSymbol)
        {
            Console.WriteLine("Finding Events");

        	// Reading the Data for the list of Symbols.	
            var timeofday = TimeSpan.FromHours(16);
            var timestamps = Nyse.GetTradingDates(startday, endday, timeofday).ToList();
            var marketDays = timestamps.Count();
            var dataobj = DataAccess.GetInstance();

            // Reading the Data
            DateTime start = DateTime.Now;
            MarketPrices close = dataobj.GetMarketData(symbols1, closefield, new TimeSeries(timestamps));
            Debug.WriteLine(DateTime.Now.Subtract(start));
	
	        // Completing the Data - Removing the NaN values from the Matrix
            close.ReplaceNanFill(FillDirection.Forward).ReplaceNanFill(FillDirection.Backward);

            // Calculating Daily Returns for the Market
            var marketList = close.ColumnList(marketSymbol);
            IList<float> spyValues = marketList.Returnize();

        	// Calculating the Returns of the Stock Relative to the Market 
	        // So if a Stock went up 5% and the Market rised 3%. Then the return relative to market is 2%
            var marketNeutralDailyMovement = MarketRelativeReturns(close, spyValues);

            var eventMatrix = new ConcurrentDictionary<string, IList<int>>();
            // Zero the matrix
            foreach (var symbol in close.Symbols)
                eventMatrix[symbol] = ListUtil.RepeatedDefault<int>(marketDays);

	        // Generating the Event Matrix
	        // Event described is : Market falls more than 3% plus the stock falls 5% more than the Market
	        // Suppose : The market fell 3%, then the stock should fall more than 8% to mark the event.
	        // And if the market falls 5%, then the stock should fall more than 10% to mark the event.
            DetectEvents(close, marketDays, spyValues, marketNeutralDailyMovement, eventMatrix, timestamps);

            return eventMatrix;
        }

        private static void DetectEvent(IList<float> spyValues, IDictionary<string, IList<float>> marketNeutralDM, IDictionary<string, IList<int>> eventMatrix,
                                IEnumerable<DateTime> timestamps, int dayOffset, string symbol)
        {
            // When market fall is more than 3% and also the stock compared to market is also fell by more than 5%.
            if (spyValues[dayOffset] < -0.03 && marketNeutralDM[symbol][dayOffset] < -0.05)
            {
                //overwriting by the bit, marking the event
                eventMatrix[symbol][dayOffset] = 1;

                Console.WriteLine("Event : " + symbol + " " + timestamps.ToList()[dayOffset]);
            }
        }

        private static void DetectEvents(MarketPrices close, int marketDays, IList<float> spyValues, IDictionary<string, IList<float>> marketNeutralDM,
                                         IDictionary<string, IList<int>> eventMatrix, List<DateTime> timestamps)
        {
            Parallel.ForEach(close.Symbols,
                             symbol =>
                                 {
                                     foreach (int i in Enumerable.Range(0, marketDays))
                                     {
                                         DetectEvent(spyValues, marketNeutralDM, eventMatrix, timestamps, i, symbol);
                                     }
                                 });
        }

        private static ConcurrentDictionary<string, IList<float>> MarketRelativeReturns(MarketPrices close, IList<float> spyValues)
        {
            var marketNeutralDailyMovement = new ConcurrentDictionary<string, IList<float>>();
            Parallel.ForEach(close.Symbols,
                             (symbol, loopState) =>
                                 {
                                     var priceList = close.ColumnList(symbol);
                                     if (priceList == null)
                                         loopState.Break();
                                     else
                                     {
                                         IList<float> dailyReturns = priceList.Returnize();
                                         IList<float> marketRelative = dailyReturns.Select((d, i) => (float)(d - spyValues[i])).ToList();
                                         marketNeutralDailyMovement[symbol] = marketRelative;
                                     }
                                 });
            return marketNeutralDailyMovement;
        }
    }
}
